import sys
from functools import partial
from typing import List

from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QHBoxLayout, QLabel, QLineEdit

from view.components.ui_pageWidget import Ui_PageWidget


class PageWidget(QWidget, Ui_PageWidget):
    # 数据发生变化时发送消息
    changed = pyqtSignal(dict)
    # 每页显示多少行的选项
    _SIZE_OPTION = ['5', '10', '20', '50']

    # 不省略时的最大页码按钮数量，超过这个大小时开始省略部分按钮
    _BTN_CNT_MAX = 8
    # 省略时中心被选中的按钮左右两边页码按钮的范围，超过这个范围并且不是1或最后一页的页码都会被省略号代替
    _BTN_CNT_CENTRAL = 2
    # 展示页码按钮的样式
    _BTN_STYLE_UNCHECKED = \
        """
        .QPushButton {
          max-width:36px;
        }
        """

    # 默认页码
    _DEFAULT_PAGE_NO = 1
    # 默认页数
    _DEFAULT_PAGE_SIZE = 10
    # _BTN_STYLE_CHECKED = \
    #     """
    #     .QPushButton {
    #       max-width:36px;
    #       background:rgb(186, 189, 182);
    #     }
    #     """

    _LABEL_TEXT_ELLIPSIS = '···'

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        # 初始化基本参数
        self._pageNum = self._DEFAULT_PAGE_NO
        self._pageSize = self._DEFAULT_PAGE_SIZE
        self._totalPage = 0
        self._total = 0
        # self._btnLast = None

        # 初始化按钮列表
        # TODO 按钮组只有一个按钮时仍然可以弹起按下
        # self._btnZero = self._getBtn(0)
        # self.btnLayout.addWidget(self._btnZero)
        # self._btnZero.hide()
        button = self._getBtn(self._DEFAULT_PAGE_NO)

        # _widgets列表是实现动态页码、展示省略号的关键
        # 每一项是一个元组，元组的第一项是QButton或QLabel，QButton就是页码按钮，QLabel是省略号的标签
        # 元组的第二项表示按钮对应的页码，如果不是按钮就置为空
        # 分页器的第一个按钮会始终存在，所以将其直接存在列表中，特殊处理
        self._widgets: List[(QWidget, int)] = [(button, self._DEFAULT_PAGE_NO)]
        # self._btnGroup = QButtonGroup()
        # self._btnGroup.addButton(button)
        button.clicked.connect(partial(self._setpageNum, self._DEFAULT_PAGE_NO))
        self.btnLayout.addWidget(button)

        # 初始化每页显示数量的下拉选择框
        self.cmbSize.clear()
        self.cmbSize.addItems(self._SIZE_OPTION)
        self.cmbSize.setCurrentIndex(1)
        self.cmbSize.currentIndexChanged.connect(lambda idx: self._resize(int(self._SIZE_OPTION[idx])))

        # 注册跳转编辑框按钮回车的监听时间
        self.editPage.returnPressed.connect(lambda: self.setpageNum())

        # 注册前后翻页按钮的监听事件
        self.btnPrev.clicked.connect(lambda: self.addPage(-1))
        self.btnNext.clicked.connect(lambda: self.addPage(1))

    def _resize(self, size):
        """
        重新设置每页显示条数，即size。需要更新数据并初始化页码按钮
        :param size: 新的size
        """
        # print(size)
        self._pageSize = size
        self._updateData()
        self._initpageNums()

    def setPageSizeIndex(self, index):
        """
        设置pageSize索引
            0 -> 5
            1 -> 10
            2 -> 20
            3 -> 50
        """
        if index < len(self._SIZE_OPTION):
            self.cmbSize.setCurrentIndex(index)

    def setTotal(self, total):
        """
        重新设置数据总数
        :param total: 新的数据总数
        """
        self._total = total
        pageNum, total = self._pageNum, self._totalPage
        self._updateData()
        # 只要新的页码或总页数与旧的不一致就需要重新生成页码按钮
        if (pageNum, total) != (self._pageNum, self._totalPage):
            self._generatepageNums()

    def setpageNum(self):
        """
        暴露的设置页码函数，通过内置的编辑框进行跳转
        """
        try:
            # 检查输入是否合法
            pageNum = int(self.editPage.text())
        except ValueError:
            return
        if pageNum > self._totalPage:
            pageNum = self._totalPage
        if pageNum != self._pageNum:
            self._pageNum = pageNum
            self._generatepageNums(pageNum)

    def _setpageNum(self, pageNum):
        """
        内置的设置页码函数，作为页码按钮点击事件的回调函数
        :param pageNum: 被点击的页码
        """
        if self._pageNum == pageNum:
            return
        # print(pageNum, self._pageNum)

        # 如果数据总数没有超过最大按钮数就不需要重新生成按钮
        if self._totalPage <= self._BTN_CNT_MAX:
            self._pageNum = pageNum
            self._widgets[pageNum - 1][0].setChecked(True)
            self._onChange()
        else:
            self._pageNum = pageNum
            self._generatepageNums(pageNum)
            # self._onChange()

    def getPageInfo(self):
        """
        暴露的获取分页器参数的函数
        :return: 返回pageNum、pageSize组成的dict
        """
        return {
            'pageNum': self._pageNum,
            'pageSize': self._pageSize,
        }

    def _onChange(self):
        """
        数据改变时发送信号
        """
        self.changed.emit(self.getPageInfo())

    def _updateData(self):
        """
        数据变化时重新通过计算更新参数
        """
        self._totalPage = (self._total - 1) // self._pageSize + 1
        if self._totalPage == 0:
            self._totalPage = 1
        if self._pageNum > self._totalPage:
            self._pageNum = 1

    def _updateWidgets(self, pageNum=None):
        """
        核心函数，更新内置的_widgets列表
        :param pageNum: 即将要选中的页码，该参数有效时，将返回新的列表中，将要被选中的页码按钮的引用
        :return: 选中的页码按钮的引用
        """
        self._widgets = self._widgets[:1]
        centralBtn = None
        # 如果总页数小于最大页数，一个一个放按钮即可
        if self._totalPage <= self._BTN_CNT_MAX:
            for i in range(2, self._totalPage + 1):
                button = self._getBtn(i)
                self._widgets.append((button, i))
                # pageNum有效时获取页码按钮的引用
                if i == pageNum:
                    centralBtn = button
        else:
            idx = self._pageNum
            # 按钮左边界
            left = max(1, idx - self._BTN_CNT_CENTRAL)
            # 按钮右边界
            right = min(left + (self._BTN_CNT_CENTRAL << 1), self._totalPage)
            # 如果右边界是最大按钮数，修正左边界
            if right == self._totalPage:
                left = max(1, right - (self._BTN_CNT_CENTRAL << 1))
            # 左边界超过2时添加省略号
            if left > 2:
                self._widgets.append((self._getLabelEllipsis(), None))
            # 将左右边界内的每一项全部作为页码按钮添加到列表
            for i in range(max(2, left), right + 1):
                button = self._getBtn(i)
                self._widgets.append((button, i))
                if i == pageNum:
                    centralBtn = button
            # 右边界小于最大页数时添加省略号
            if right < self._totalPage - 1:
                self._widgets.append((self._getLabelEllipsis(), None))
            # 右边界不是最后一页，添加最后一页的页码
            if right < self._totalPage:
                self._widgets.append((self._getBtn(self._totalPage), self._totalPage))
        return centralBtn

    def _initpageNums(self):
        """
        resize时重置组件，将页码设置为第一页
        """
        self._updateWidgets()
        self._drawpageNums()
        # 防止pageNum等于1时_setpageNum被忽略
        self._pageNum = 0
        self._widgets[0][0].click()

    def _generatepageNums(self, pageNum=None):
        """
        总页数不应发生变化时，刷新组件
        :param pageNum: 即将被选中的页码
        """
        centralBtn = None if pageNum is not None and pageNum > 1 else self._widgets[0][0]
        button = self._updateWidgets(pageNum)
        if button is not None:
            centralBtn = button

        self._drawpageNums(centralBtn)

    def _drawpageNums(self, button=None):
        """
        根据_widgets组件绘制按钮，调用此函数前应先调用_updateWidgets函数
        :param button: 如果是一个有效的按钮，将其设为选中并触发_onChange函数，
        :return:
        """
        # 删除布局中的所有组件，页码为1的按钮除外
        while True:
            # 因此下标从1开始
            child = self.btnLayout.takeAt(1)
            if child is None:
                break
            if child.widget():
                child.widget().setParent(None)
            del child
        # 将_widgets的组件绘制到布局中
        # 同理，下标从1开始
        for item, i in self._widgets[1:]:
            self.btnLayout.addWidget(item)
            # 如果是按钮需要添加监听事件
            if i:
                item.clicked.connect(partial(self._setpageNum, i))
        if button:
            button.setChecked(True)
            self._onChange()

    def _getBtn(self, pageNum):
        """
        获取一个页码对应的按钮
        :param pageNum: 页码
        :return: 对应的QPushButton
        """
        button = QPushButton()
        button.setText(str(pageNum))
        # 将其设为可选的
        button.setCheckable(True)
        # 将其设为互斥的
        button.setAutoExclusive(True)
        button.setStyleSheet(self._BTN_STYLE_UNCHECKED)
        return button

    def _getLabelEllipsis(self):
        """
        获取一个展示省略号的标签
        :return: 展示省略号的QLabel
        """
        label = QLabel()
        label.setText(self._LABEL_TEXT_ELLIPSIS)
        return label

    def addPage(self, num):
        """
        翻页函数，根据当前页和增页数进行跳转
        :param num: 需要增加的页数，小于0表示向前翻页
        """
        newpageNum = self._pageNum + num
        # 防止页码溢出
        if num > 0:
            newpageNum = min(self._totalPage, newpageNum)
        else:
            newpageNum = max(1, newpageNum)
        # 如果页码没有发生改变则不需要重新跳转
        if newpageNum != self._pageNum:
            self._setpageNum(newpageNum)


if __name__ == '__main__':
    app = QApplication(sys.argv)  # 创建GUI应用程序
    mainWidget = QWidget()
    layout = QHBoxLayout()

    widget = PageWidget()  # 创建窗体
    widget.changed.connect(print)
    widget.setTotal(0)

    edit = QLineEdit('0')
    edit.returnPressed.connect(lambda: widget.setTotal(int(edit.text())))

    layout.addWidget(edit)
    layout.addWidget(widget)
    layout.setStretch(0, 1)
    mainWidget.setLayout(layout)
    mainWidget.show()
    sys.exit(app.exec_())
